package lumis.portal.report;

import java.io.File;
import java.util.ArrayList;

import javax.persistence.EntityManager;
import javax.persistence.NoResultException;

import org.w3c.dom.Document;
import org.w3c.dom.Node;

import lumis.portal.PortalException;
import lumis.portal.PortalObjectNotFoundException;
import lumis.portal.acl.PortalPermissions;
import lumis.portal.authentication.SessionConfig;
import lumis.portal.filesystem.PortalBaseFolder;
import lumis.portal.filesystem.PortalFile;
import lumis.portal.filesystem.PortalFilePath;
import lumis.portal.manager.ManagerException;
import lumis.portal.manager.ManagerFactory;
import lumis.portal.manager.ManagerStringResource;
import lumis.portal.transaction.PortalTransactionFactory;
import lumis.portal.util.PortalUtilInternal;
import lumis.util.IResource;
import lumis.util.XmlUtil;

/**
 * Implementation of report management operations.
 *
 * @version $Revision: 12535 $ $Date: 2011-02-09 11:33:22 -0200 (Wed, 09 Feb 2011) $
 * @since 4.2.0
 */
public class ReportManager implements IReportManager
{
	private IResource stringsResource = new ManagerStringResource();
	{
		stringsResource.addResourcePath("lumis/service/portalmanagement/reporttype/strings/strings");
	}

	// -- Report -- // 
	
	public Report getReport(String reportId) throws PortalException
	{
		Report report = ManagerFactory.getEntityManager().find(Report.class, reportId);
		if (report != null)
			return report;
		else
			throw new PortalObjectNotFoundException("Report with id='" + reportId + "' not found");
	}
	
	// -- ReportType -- //
	
	public ReportType getReportType(String reportTypeId) throws PortalException
	{
		ReportType reportType = ManagerFactory.getEntityManager().find(ReportType.class, reportTypeId);
		if (reportType != null)
			return reportType;
		else
			throw new PortalObjectNotFoundException("ReportType with id='" + reportTypeId + "' not found");
	}
	
	public void register(String baseUrl) throws PortalException
	{
		// validate permission
		ManagerFactory.getPortalAclManager().checkPermission(SessionConfig.getCurrentSessionConfig(), 
				PortalPermissions.MANAGE_PORTAL, PortalTransactionFactory.getCurrentTransaction());
		
		// read report type definition file
		Document reportTypeDocument = PortalUtilInternal.getDefinitionDocument(baseUrl + "/reportdefinition.xml");
		
		// TODO: validate the report type definition

		// add or update the report type
		Node reportTypeXmlNode = XmlUtil.selectSingleNode("report", reportTypeDocument.getDocumentElement());
		if (reportTypeXmlNode != null)
			addOrUpdateReportType(reportTypeXmlNode, baseUrl);
	}
	
	private void addOrUpdateReportType(Node reportTypeXmlNode, String reportTypeConfigPath) throws PortalException
	{
		String reportTypeId = XmlUtil.readAttributeString("id", reportTypeXmlNode);

		// validate if there is another report with other id in the same baseUrl
		try
		{
			String reportTypeIdInBase = (String)ManagerFactory.getEntityManager().createQuery(
					"select rt.id from ReportType rt where " +
					"rt.id != :id and rt.baseUrl = :baseUrl")
					.setParameter("id", reportTypeId)
					.setParameter("baseUrl", reportTypeConfigPath)
					.getSingleResult();
			if (reportTypeIdInBase != null)
				throw new ManagerException("STR_CANNOT_CHANGE_REPORT_TYPE_ID;"+reportTypeIdInBase+";"+reportTypeId);
		}
		catch(NoResultException e)
		{
			// validation completed OK
		}
		
		boolean adding = false;
		EntityManager entityManager = ManagerFactory.getEntityManager();
		ReportType reportType = entityManager.find(ReportType.class, reportTypeId);
		if (reportType == null)
		{
			reportType = new ReportType();
			adding = true;
		}
		reportType.setId(reportTypeId);
		
		reportType.setReportPath(XmlUtil.readNodeString("reportPath", reportTypeXmlNode));
		reportType.setDataSourceProvider(XmlUtil.readNodeString("dataSourceProvider", reportTypeXmlNode));
		reportType.setBaseUrl(reportTypeConfigPath);
		
		Node configParametersNode = XmlUtil.selectSingleNode("configParameters", reportTypeXmlNode);
		reportType.setConfigParametersDefinitionNode(configParametersNode);
		
		Node runtimeParametersNode = XmlUtil.selectSingleNode("runtimeParameters", reportTypeXmlNode);
		reportType.setRuntimeParametersDefinitionNode(runtimeParametersNode);

		// add or update the reportType in persistence, and obtain the persistent instance
		if (adding)
		{
			entityManager.persist(reportType);
		}
		else
		{
			for (ReportTypeParameter parameter: new ArrayList<ReportTypeParameter>(reportType.getParameters()))
				parameter.delete();
			entityManager.flush();
		}
			
		// add parameters specified in the report type definition
		Node parameterNodes[] = XmlUtil.selectNodes("parameters/parameter", reportTypeXmlNode);
		for (Node parameterNode: parameterNodes)
		{
			ReportTypeParameter parameter = reportType.addParameter();
			parameter.setName(XmlUtil.readAttributeString("name", parameterNode));
			parameter.setType(XmlUtil.readAttributeString("type", parameterNode));
			parameter.setValue(XmlUtil.readNodeString("value", parameterNode));
		}
	}
	
	public void unregister(ReportType reportType) throws PortalException
	{
		if (reportType == null)
			throw new IllegalArgumentException("reportType cannot be null");
		
		// validate permission
		ManagerFactory.getPortalAclManager().checkPermission(SessionConfig.getCurrentSessionConfig(), 
				PortalPermissions.MANAGE_PORTAL, PortalTransactionFactory.getCurrentTransaction());

		// check if there exists a report for the report type
		EntityManager entityManager = ManagerFactory.getEntityManager();
		Number count = (Number)entityManager.createQuery(
				"select count(*) from Report r where r.reportType = ?")
				.setParameter(1, reportType)
				.getSingleResult();
		if (count.intValue() > 0)
			throw new ManagerException("STR_REPORT_TYPE_IS_INSTANTIATED", stringsResource);

		// delete the report type from persistence
		entityManager.remove(reportType);
	}
	
	public void registerDefaultTypes() throws PortalException
	{
		// for each directory inside <def>/lumis/report
		PortalFile reportBaseDir = new PortalFile(new PortalFilePath(PortalBaseFolder.DEF, "lumis/report"));
		for (File reportDir: reportBaseDir.listFiles())
		{
			if (reportDir.isDirectory())
			{
				// register the corresponding report
				String baseUrl = new PortalFile(reportDir).getPortalFilePath().getRelativePath(); 
				register(baseUrl);
			}
		}
	}
}